package com.ck.client.core.interceptor;

import com.alibaba.fastjson.JSON;
import com.ck.client.dao.DeviceInfoMapper;
import com.ck.common.model.bo.DeviceKeyMsg;
import com.ck.core.annotation.ClientPermissionRequired;
import com.ck.core.cache.redis.RedisCacheDao;
import com.ck.core.constant.BusinessConstant;
import com.ck.core.constant.CacheConstant;
import com.ck.core.constant.ClientConstant;
import com.ck.core.constant.profile.ProfileConstant;
import com.ck.core.util.MD5Util;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

@Component
public class CkClientPermissionInterceptor implements HandlerInterceptor{
	private static final String REGISTER_URL = "/ckclient/register";
	private static final String PARAM_TIMESTAMP = "timestamp";
	private static final String HEADER_SIGN = "sign";
	private static final String HEADER_MAC = "mac";
	private static final String HEADER_DEVICE_MODEL = "devicemodel";
	private static final String HEADER_APPVERSION = "appversion";
	private static final String HEADER_APPVERSIONCODE = "appversioncode";
	private static final String HEADER_FIRMWAREVERSION = "firmwareversion";
	private static final String HEADER_DEVICECODE = "devicecode";
	private static final String HEADER_UNIQUE_DEVICECODE = "uniquedevicecode";
	private static final String HEADER_TOKEN = "token";

	@Autowired
	private DeviceInfoMapper deviceInfoMapper;
	@Autowired
	private RedisCacheDao redisCacheDao;
    @Autowired
    private Environment env;
	private static Logger logger = LoggerFactory.getLogger(CkClientPermissionInterceptor.class);
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
		Map<String,Object> map = new HashMap<>();
		if ( hasPermission(handler,request,map) ) {
			return true;
		} else {
			try {
				ObjectMapper json = new ObjectMapper();
				response.setContentType("text/javascript;charset=utf-8");
				response.getWriter().write(json.writeValueAsString(map));
			} catch (IOException e1) {
				logger.error(e1.getMessage(), e1);
			}
			return false;
		}
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		// doing nothing
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		// doing nothing
	}

	/**
     * 客户端是否有访问权限
     * @param handler
     * @param map
	 * @return
     */
    private boolean hasPermission(Object handler, HttpServletRequest request, Map<String, Object> map) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            ClientPermissionRequired clientPermissionRequired = handlerMethod.getMethod().getAnnotation(ClientPermissionRequired.class);
            // 如果方法上的注解为空 则获取类的注解
            if (clientPermissionRequired == null) {
            	clientPermissionRequired = handlerMethod.getMethod().getDeclaringClass().getAnnotation(ClientPermissionRequired.class);
            }
            // 如果标记了注解，则判断权限
            if (clientPermissionRequired != null) {
				logger.info("###@request URI "  + request.getRequestURI() + " ###@request params "  + JSON.toJSONString(request.getParameterMap()) + " ###@request headers : " + JSON.toJSONString(getHeadersInfo(request)));

				//检查必要参数
				String missingParam = checkNecessaryHeaderParams(request);
                if(StringUtils.isNotBlank(missingParam)){
                    // 1、必传参数缺失，拦截。
                    map.put(ClientConstant.CODE, ClientConstant.CODE_PARAM_VALIDATE_FAIL);
                    map.put(ClientConstant.MSG, "必要参数[" + missingParam + "]缺失！");
                    logger.warn("============clientPermissionInterceptor invoke, missing necessary parameters: 【" + missingParam + "】");
                    return false;
                }

                // 非本地环境验证签名是否合法
                if ( !ProfileConstant.DEV.equals(env.getActiveProfiles()[0]) && !request.getHeader(HEADER_SIGN).equals(MD5Util.digest(constructDigestMessage(request))) ) {
					// 2、签名验证不匹配
					map.put(ClientConstant.CODE, ClientConstant.CODE_SIGN_VALIDATE_FAIL);
					map.put(ClientConstant.MSG, "签名验证不匹配！");
                    logger.warn("============clientPermissionInterceptor invoke, sign not match !!!");
                    return false;
                }

				String uniqueDeviceCode = request.getHeader(HEADER_UNIQUE_DEVICECODE);
				String deviceCode = request.getHeader(HEADER_DEVICECODE);
            	// 根据序列号获取缓存中的关键信息
				DeviceKeyMsg deviceKeyMsg = (DeviceKeyMsg) redisCacheDao.getCache(CacheConstant.CACHE_NAME_DEVICE_KEY_MSG, uniqueDeviceCode);
				// 如果缓存不存在，则从数据库获取一次
				if ( deviceKeyMsg == null ) {

					// 老的唯一编码得到的设备信息
					DeviceKeyMsg oldDeviceInfo = deviceInfoMapper.getDeviceKeyMsg(deviceCode);
					// 新的唯一编码得到的设备信息
					DeviceKeyMsg newDeviceInfo = deviceInfoMapper.getDeviceKeyMsg(uniqueDeviceCode);

					// 如果老的唯一编码存在于数据库中，且新的编码未入库过，则用新的uuid生成的唯一设备编码覆盖老的唯一编码
					if ( oldDeviceInfo != null && newDeviceInfo == null ) {
						deviceInfoMapper.replaceDeviceCode(deviceCode,uniqueDeviceCode);
					}

					// 获取替换后的设备关键信息
					deviceKeyMsg = deviceInfoMapper.getDeviceKeyMsg(uniqueDeviceCode);
					if (deviceKeyMsg != null) {
						redisCacheDao.putCache(CacheConstant.CACHE_NAME_DEVICE_KEY_MSG, uniqueDeviceCode, deviceKeyMsg);
					}
				}
				// 3、设备未提前入库，非法设备请求，拦截。
				if ( deviceKeyMsg == null ) {
					map.put(ClientConstant.CODE, ClientConstant.CODE_PERMISSION_DENIED);
					map.put(ClientConstant.MSG, "非系统内设备");
					logger.warn("============clientPermissionInterceptor invoke, device unknown 【"+ uniqueDeviceCode +"】!!!");
					return false;
				}
                // 4、系统存在token,上传参数中无token或token不匹配，拦截并提示让后台人员解绑。
//                String systemToken = deviceKeyMsg.getToken();
//				String deviceToken = request.getHeader(HEADER_TOKEN);
//                if ( StringUtils.isNotBlank(systemToken) && !systemToken.equals(deviceToken)) {
//                    map.put(ClientConstant.CODE, ClientConstant.CODE_TOKEN_INVALID);
//                    map.put(ClientConstant.MSG, "token失效，请联系后台管理人员解绑");
//                    logger.warn("============clientPermissionInterceptor invoke, token invalid 【"+ uniqueDeviceCode +"】!!!");
//                    return false;
//                }
                // 5、系统[不存在]token，即设备还未注册或已解绑的情况，仅能访问注册接口。
//                if ( StringUtils.isBlank(systemToken) && !REGISTER_URL.equals(request.getRequestURI()) ) {
//                    map.put(ClientConstant.CODE, ClientConstant.CODE_DEVICE_NOT_REGIST);
//                    map.put(ClientConstant.MSG, "请重新注册");
//                    logger.warn("============clientPermissionInterceptor invoke, device not regist 【"+ uniqueDeviceCode +"】!!!");
//                    return false;
//                }

                // 唯一编码写入request
				request.setAttribute(BusinessConstant.CHAR_DEVICE_CODE, uniqueDeviceCode);
			}
        }
        return true;
    }

	/**
     * 需要加密的串：requestURI + 参数（字符串升序排列）
     */
    private String constructDigestMessage(HttpServletRequest request) {
        Enumeration<String> requestNames = request.getParameterNames();
        // 使用treeset 自然排序
        Set<String> paramNames = new TreeSet<>();
        while (requestNames != null && requestNames.hasMoreElements()) {
            paramNames.add(requestNames.nextElement());
        }
        StringBuilder retVal = new StringBuilder(request.getRequestURI());
        int count = 0;
        for (String name : paramNames) {
            if (count++ > 0) {
                retVal.append("&");
            } else {
                retVal.append("@");
            }
            retVal.append(name).append("=").append(request.getParameter(name));
        }
        return retVal.toString();
    }

    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    /**
     * 检查header必要参数是否缺失
     * @param request
     * @return
     */
    private String checkNecessaryHeaderParams(HttpServletRequest request){
		if ( StringUtils.isBlank(request.getHeader(HEADER_SIGN))) {
			return HEADER_SIGN;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_MAC))) {
			return HEADER_MAC;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_DEVICE_MODEL))) {
			return HEADER_DEVICE_MODEL;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_APPVERSION))) {
			return HEADER_APPVERSION;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_APPVERSIONCODE))) {
			return HEADER_APPVERSIONCODE;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_FIRMWAREVERSION))) {
			return HEADER_FIRMWAREVERSION;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_DEVICECODE))) {
			return HEADER_DEVICECODE;
		}
		if ( StringUtils.isBlank(request.getHeader(HEADER_UNIQUE_DEVICECODE))) {
			return HEADER_UNIQUE_DEVICECODE;
		}
		if ( StringUtils.isBlank(request.getParameter(PARAM_TIMESTAMP))) {
			return PARAM_TIMESTAMP;
		}
		return "";
	}
}
